const WKBGeometryType = {
    POINT: 1,
    LINE_STRING: 2,
    POLYGON: 3,
    MULTI_POINT: 4,
    MULTI_LINE_STRING: 5,
    MULTI_POLYGON: 6,
    GEOMETRY_COLLECTION: 7,
  
    /*
    CIRCULAR_STRING: 8,
    COMPOUND_CURVE: 9,
    CURVE_POLYGON: 10,
  
    MULTI_CURVE: 11,
    MULTI_SURFACE: 12,
    CURVE: 13,
    SURFACE: 14,
    */
  
    POLYHEDRAL_SURFACE: 15,
    TIN: 16,
    TRIANGLE: 17,
};
class WkbReader {
    /**
     * @param {DataView} view source to read
     */
    constructor(view) {
      /** @private */
      this.view_ = this.getDataView(view);
  
      /**
       * @type {number}
       * @private
       */
      this.pos_ = 0;
  
      /**
       * @type {boolean}
       * @private
       */
      this.initialized_ = false;
  
      /**
       * @type {boolean}
       * @private
       */
      this.isLittleEndian_ = false;
  
      /**
       * @type {boolean}
       * @private
       */
      this.hasZ_ = false;
  
      /**
       * @type {boolean}
       * @private
       */
      this.hasM_ = false;
  
      /**
       * @type {number|null}
       * @private
       */
      this.srid_ = null;
  
      /**
       * @type {import("../geom/Geometry.js").GeometryLayout}
       * @private
       */
      this.layout_ = 'XY';
    }
  
    /**
     * @return {number} value
     */
    readUint8() {
      return this.view_.getUint8(this.pos_++);
    }
  
    /**
     * @param {boolean} [isLittleEndian] Whether read value as little endian
     * @return {number} value
     */
    readUint32(isLittleEndian) {
      return this.view_.getUint32(
        (this.pos_ += 4) - 4,
        isLittleEndian !== undefined ? isLittleEndian : this.isLittleEndian_
      );
    }
  
    /**
     * @param {boolean} [isLittleEndian] Whether read value as little endian
     * @return {number} value
     */
    readDouble(isLittleEndian) {
      return this.view_.getFloat64(
        (this.pos_ += 8) - 8,
        isLittleEndian !== undefined ? isLittleEndian : this.isLittleEndian_
      );
    }
  
    /**
     * @return {import('../coordinate.js').Coordinate} coords for Point
     */
    readPoint() {
      /** @type import('../coordinate.js').Coordinate */
      const coords = [];
  
      coords.push(this.readDouble());
      coords.push(this.readDouble());
      if (this.hasZ_) {
        coords.push(this.readDouble());
      }
      if (this.hasM_) {
        coords.push(this.readDouble());
      }
  
      return coords;
    }
  
    /**
     * @return {Array<import('../coordinate.js').Coordinate>} coords for LineString / LinearRing
     */
    readLineString() {
      const numPoints = this.readUint32();
  
      /** @type Array<import('../coordinate.js').Coordinate> */
      const coords = [];
      for (let i = 0; i < numPoints; i++) {
        coords.push(this.readPoint());
      }
  
      return coords;
    }
  
    /**
     * @return {Array<Array<import('../coordinate.js').Coordinate>>} coords for Polygon like
     */
    readPolygon() {
      const numRings = this.readUint32();
  
      /** @type Array<Array<import('../coordinate.js').Coordinate>> */
      const rings = [];
      for (let i = 0; i < numRings; i++) {
        rings.push(this.readLineString()); // as a LinearRing
      }
  
      return rings;
    }
  
    /**
     * @param {number} [expectedTypeId] Expected WKB Type ID
     * @return {number} WKB Type ID
     */
    readWkbHeader(expectedTypeId) {
      const byteOrder = this.readUint8();
      const isLittleEndian = byteOrder > 0;
  
      const wkbType = this.readUint32(isLittleEndian);
      const wkbTypeThousandth = Math.floor((wkbType & 0x0fffffff) / 1000);
      const hasZ =
        Boolean(wkbType & 0x80000000) ||
        wkbTypeThousandth === 1 ||
        wkbTypeThousandth === 3;
      const hasM =
        Boolean(wkbType & 0x40000000) ||
        wkbTypeThousandth === 2 ||
        wkbTypeThousandth === 3;
      const hasSRID = Boolean(wkbType & 0x20000000);
      const typeId = (wkbType & 0x0fffffff) % 1000; // Assume 1000 is an upper limit for type ID
      const layout = /** @type {import("../geom/Geometry.js").GeometryLayout} */ (
        ['XY', hasZ ? 'Z' : '', hasM ? 'M' : ''].join('')
      );
  
      const srid = hasSRID ? this.readUint32(isLittleEndian) : null;
  
      if (expectedTypeId !== undefined && expectedTypeId !== typeId) {
        throw new Error('Unexpected WKB geometry type ' + typeId);
      }
  
      if (this.initialized_) {
        // sanity checks
        if (this.isLittleEndian_ !== isLittleEndian) {
          throw new Error('Inconsistent endian');
        }
        if (this.layout_ !== layout) {
          throw new Error('Inconsistent geometry layout');
        }
        if (srid && this.srid_ !== srid) {
          throw new Error('Inconsistent coordinate system (SRID)');
        }
      } else {
        this.isLittleEndian_ = isLittleEndian;
        this.hasZ_ = hasZ;
        this.hasM_ = hasM;
        this.layout_ = layout;
        this.srid_ = srid;
        this.initialized_ = true;
      }
  
      return typeId;
    }
  
    /**
     * @param {number} typeId WKB Type ID
     * @return {any} values read
     */
    readWkbPayload(typeId) {
      switch (typeId) {
        case WKBGeometryType.POINT:
          return this.readPoint();
  
        case WKBGeometryType.LINE_STRING:
          return this.readLineString();
  
        case WKBGeometryType.POLYGON:
        case WKBGeometryType.TRIANGLE:
          return this.readPolygon();
  
        case WKBGeometryType.MULTI_POINT:
          return this.readMultiPoint();
  
        case WKBGeometryType.MULTI_LINE_STRING:
          return this.readMultiLineString();
  
        case WKBGeometryType.MULTI_POLYGON:
        case WKBGeometryType.POLYHEDRAL_SURFACE:
        case WKBGeometryType.TIN:
          return this.readMultiPolygon();
  
        case WKBGeometryType.GEOMETRY_COLLECTION:
          return this.readGeometryCollection();
  
        default:
          throw new Error(
            'Unsupported WKB geometry type ' + typeId + ' is found'
          );
      }
    }
  
    /**
     * @param {number} expectedTypeId Expected WKB Type ID
     * @return {any} values read
     */
    readWkbBlock(expectedTypeId) {
      return this.readWkbPayload(this.readWkbHeader(expectedTypeId));
    }
  
    /**
     * @param {Function} reader reader function for each item
     * @param {number} [expectedTypeId] Expected WKB Type ID
     * @return {any} values read
     */
    readWkbCollection(reader, expectedTypeId) {
      const num = this.readUint32();
  
      const items = [];
      for (let i = 0; i < num; i++) {
        const result = reader.call(this, expectedTypeId);
        if (result) {
          items.push(result);
        }
      }
  
      return items;
    }
  
    /**
     * @return {Array<import('../coordinate.js').Coordinate>} coords for MultiPoint
     */
    readMultiPoint() {
      return this.readWkbCollection(this.readWkbBlock, WKBGeometryType.POINT);
    }
  
    /**
     * @return {Array<Array<import('../coordinate.js').Coordinate>>} coords for MultiLineString like
     */
    readMultiLineString() {
      return this.readWkbCollection(
        this.readWkbBlock,
        WKBGeometryType.LINE_STRING
      );
    }
  
    /**
     * @return {Array<Array<Array<import('../coordinate.js').Coordinate>>>} coords for MultiPolygon like
     */
    readMultiPolygon() {
      return this.readWkbCollection(this.readWkbBlock, WKBGeometryType.POLYGON);
    }
  
    /**
     * @return {Array<import('../geom/Geometry.js').default>} array of geometries
     */
    readGeometryCollection() {
      return this.readWkbCollection(this.readGeometry);
    }
  
    /**
     * @return {import('../geom/Geometry.js').default} geometry
     */
    readGeometry() {
      const typeId = this.readWkbHeader();
      const result = this.readWkbPayload(typeId);

      console.log(typeId);
      console.log(result);
  
      switch (typeId) {
        case WKBGeometryType.POINT:
          return new Point(
            /** @type {import('../coordinate.js').Coordinate} */ (result),
            this.layout_
          );
  
        case WKBGeometryType.LINE_STRING:
          return new LineString(
            /** @type {Array<import('../coordinate.js').Coordinate>} */ (result),
            this.layout_
          );
  
        case WKBGeometryType.POLYGON:
        case WKBGeometryType.TRIANGLE:
          return new Polygon(
            /** @type {Array<Array<import('../coordinate.js').Coordinate>>} */ (
              result
            ),
            this.layout_
          );
  
        case WKBGeometryType.MULTI_POINT:
          return new MultiPoint(
            /** @type {Array<import('../coordinate.js').Coordinate>} */ (result),
            this.layout_
          );
  
        case WKBGeometryType.MULTI_LINE_STRING:
          return new MultiLineString(
            /** @type {Array<Array<import('../coordinate.js').Coordinate>>} */ (
              result
            ),
            this.layout_
          );
  
        case WKBGeometryType.MULTI_POLYGON:
        case WKBGeometryType.POLYHEDRAL_SURFACE:
        case WKBGeometryType.TIN:
          // return new MultiPolygon(
          //   /** @type {Array<Array<Array<import('../coordinate.js').Coordinate>>>} */ (
          //     result
          //   ),
          //   this.layout_
          // );
          return {
              "type":"MultiPolygon","coordinates":result
          }
  
        case WKBGeometryType.GEOMETRY_COLLECTION:
          return new GeometryCollection(
            /** @type {Array<import('../geom/Geometry.js').default>} */ (result)
          );
  
        default:
          return null;
      }
    }
  
    /**
     * @return {number|null} SRID in the EWKB. `null` if not defined.
     */
    getSrid() {
      return this.srid_;
    }

    /**
     * @param {string} text source text
     * @return {DataView} decoded binary buffer
     */
    decodeHexString(text) {
      const buffer = new Uint8Array(text.length / 2);
      for (let i = 0; i < text.length / 2; i++) {
          buffer[i] = parseInt(text.substr(i * 2, 2), 16);
      }

      // console.log(new DataView(buffer.buffer), 'new DataView(buffer.buffer)==================');
      return new DataView(buffer.buffer);
    }

    /**
    * @param {string | ArrayBuffer | ArrayBufferView} source source
    * @return {DataView} data view
    */
    getDataView(source) {
      if (typeof source === 'string') {
          return this.decodeHexString(source);
      }
      if (ArrayBuffer.isView(source)) {
          if (source instanceof DataView) {
              return source;
          }
          return new DataView(source.buffer, source.byteOffset, source.byteLength);
      }
      if (source instanceof ArrayBuffer) {
          return new DataView(source);
      }
      return null;
    }
  }

  export default WkbReader;